import { reactive, ref, Ref } from 'vue'

/** 移动初始位置 */
export interface StartData {
  /** 鼠标水平距离 */
  x: number
  /** 鼠标垂直距离 */
  y: number
}

/** 移动数据，相对开始移动 */
export interface MoveData {
  /** 拖拽移动水平距离 */
  x: number
  /** 拖拽移动垂直距离 */
  y: number
}

/** 拖拽控制，用于拖拽时获取鼠标移动距离 */
export default function useDrag<S = unknown, T = unknown>({
  zoom = ref(1),
  onStart,
  onMoving,
  onEnd
}: {
  /** 缩放 */
  zoom?: Ref<number>

  /**
   * 拖拽开始
   * @description
   * - 返回初始状态，并作为 `onMoving` 的参数
   * - 如果返回 `false` 则阻止拖拽
   */
  onStart?: (
    /** 拖拽开始事件 */
    e: MouseEvent | TouchEvent,
    /** 初始位置 */
    start: StartData,
    /** 自定义传输数据 */
    transfer?: T
  ) => S | false

  /** 移动中 */
  onMoving?: (
    /** 拖拽中事件 */
    e: MouseEvent | TouchEvent,
    /** 移动数据 */
    move: MoveData,
    /** 初始状态 */
    startState: S
  ) => void

  /** 拖拽结束 */
  onEnd?: (
    /** 拖拽结束事件 */
    e: MouseEvent | TouchEvent,
    /** 是否移动过 */
    moved: boolean
  ) => void
} = {}) {
  /** 鼠标初始位置 */
  const start: StartData = { x: 0, y: 0 }

  /** 是否移动过 */
  let moved = false

  /** 初始状态，用于移动后更新状态 */
  let startState: S | false | null = null

  /** 上次移动时间戳*/
  let lastMove = 0

  /** 是否移动中 */
  const isMoving = ref(false)

  /** 拖拽开始，用于绑定节点 `@mousedown.left` 事件 */
  function dragStart(
    /** 拖拽开始事件 */
    e: MouseEvent | TouchEvent,
    /** 自定义传输数据 */
    transfer?: T
  ) {
    const isTouch = e.type.includes('touch')
    const { pageX: x, pageY: y } = (e as TouchEvent).touches?.[0] || e

    Object.assign(start, { x, y })

    moved = false

    startState = onStart?.(e, start, transfer) ?? null

    if (startState === false) return

    isMoving.value = true

    if (isTouch) {
      document.addEventListener('touchmove', dragging, { passive: false })
      document.addEventListener('touchend', dragEnd, { passive: false })
    } else {
      document.addEventListener('mousemove', dragging)
      document.addEventListener('mouseup', dragEnd)
    }
  }

  /** 拖拽移动 */
  function dragging(e: MouseEvent | TouchEvent) {
    const now = +new Date()

    // 避免拖拽时选中页面内容
    e.preventDefault()

    /** 减少执行频率 */
    if (now - lastMove < 30) return

    lastMove = now

    const { pageX: x, pageY: y } = (e as TouchEvent).touches?.[0] || e

    if (start.x !== x || start.y !== y) {
      moved = true
    }

    onMoving?.(
      e,
      {
        x: (x - start.x) / zoom.value,
        y: (y - start.y) / zoom.value
      },
      startState as S
    )
  }

  /** 拖拽结束 */
  function dragEnd(e: MouseEvent | TouchEvent) {
    const isTouch = e instanceof TouchEvent

    if (isTouch) {
      document.removeEventListener('touchmove', dragging)
      document.removeEventListener('touchend', dragEnd)
    } else {
      document.removeEventListener('mousemove', dragging)
      document.removeEventListener('mouseup', dragEnd)
    }

    startState = null
    isMoving.value = false

    onEnd?.(e, moved)
  }

  return reactive({
    /** 是否移动中 */
    isMoving,

    /** 拖拽开始，用于绑定节点 `@mousedown.left` 事件 */
    dragStart
  })
}
